<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="soulhttp-bootstrap">
      Soul网关源码解析（十四）HTTP数据同步-Bootstrap端
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      本篇文章来研究下Soul网关Bootstrap端的HTTP数据同步，看看和上篇的Websocket数据同步区别大不大
     </p>
     <h2 id="_2">
      示例启动
     </h2>
     <p>
      启动时候，我们先把Soul-admin和Soul-Bootstrap的Websocket同步给关了，开启HTTP同步，配置大致如下：
     </p>
     <p>
      Soul-Admin的HTTP同步方式配置
     </p>
     <div class="codehilite">
      <pre><span></span><code>  sync:
      http:
        enabled: true
</code></pre>
     </div>
     <p>
      Soul-Bootstrap的Http同步方式配置
     </p>
     <div class="codehilite">
      <pre><span></span><code>soul :
    file:
      enabled: true
    corss:
      enabled: true
    dubbo :
      parameter: multi
    sync:
        http:
             url : http://localhost:9095
</code></pre>
     </div>
     <p>
      启动Soul-admin、Soul-Bootstrap、Soul-Example-HTTP，开始进入源码debug
     </p>
     <h2 id="debug">
      源码Debug
     </h2>
     <p>
      首先进入属性的 CommonPluginDataSubscriber ,在插件数据更新的函数上打上断点，代码如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CommonPluginDataSubscriber</span> <span class="kd">implements</span> <span class="n">PluginDataSubscriber</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="p">(</span><span class="kd">final</span> <span class="n">PluginData</span> <span class="n">pluginData</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">subscribeDataHandler</span><span class="p">(</span><span class="n">pluginData</span><span class="p">,</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">UPDATE</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      跟踪调用栈来到数据同步初探文章中的HTTP同步模块的入口处，调用了subscribe
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PluginDataRefresh</span> <span class="kd">extends</span> <span class="n">AbstractDataRefresh</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">refresh</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"clear all plugin data cache"</span><span class="p">);</span>
            <span class="n">pluginDataSubscriber</span><span class="p">.</span><span class="na">refreshPluginDataAll</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">pluginDataSubscriber</span><span class="p">.</span><span class="na">refreshPluginDataAll</span><span class="p">();</span>
            <span class="n">data</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">pluginDataSubscriber</span><span class="p">::</span><span class="n">onSubscribe</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      继续跟，来到下面的代码，这里得到data数据，但是调用栈在这里断了。我们在refresh函数上打上断点，然后重启
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractDataRefresh</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="n">DataRefresh</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="n">Boolean</span> <span class="nf">refresh</span><span class="p">(</span><span class="kd">final</span> <span class="n">JsonObject</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">boolean</span> <span class="n">updated</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
        <span class="n">JsonObject</span> <span class="n">jsonObject</span> <span class="o">=</span> <span class="n">convert</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">!=</span> <span class="n">jsonObject</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">fromJson</span><span class="p">(</span><span class="n">jsonObject</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">updateCacheIfNeed</span><span class="p">(</span><span class="n">result</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">updated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
                <span class="n">refresh</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="na">getData</span><span class="p">());</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">updated</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      成功出现新的调用栈，我们跟踪调用栈，来到下面一个函数，这个函数看大意是所有的数据类型都调用更新一遍，考虑到HTTP是全量数据，确实有这个可能
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">DataRefreshFactory</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">executor</span><span class="p">(</span><span class="kd">final</span> <span class="n">JsonObject</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="kt">boolean</span><span class="o">[]</span> <span class="n">success</span> <span class="o">=</span> <span class="p">{</span><span class="kc">false</span><span class="p">};</span>
        <span class="c1">// 代码的调用明显和websocket不一样，看着代码大意是五个类型都有进行更新，结合HTTP请求是全量数据，感觉确实需要五个都进行缓存更新</span>
        <span class="n">ENUM_MAP</span><span class="p">.</span><span class="na">values</span><span class="p">().</span><span class="na">parallelStream</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">dataRefresh</span> <span class="o">-&gt;</span> <span class="n">success</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">dataRefresh</span><span class="p">.</span><span class="na">refresh</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
        <span class="k">return</span> <span class="n">success</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      继续跟踪调用栈，来到了一个比较大的类，里面有很多的操作，其中比较重要的是：
     </p>
     <ul>
      <li>
       第一次进入时，到Admin获取所有的数据进行缓存更新
      </li>
      <li>
       开启线程池，定时轮询，其中还有一些判断数据是否是旧的就多睡几秒的细节操作，看完有所启发
      </li>
     </ul>
     <p>
      相关代码和说明如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// It could be initialized multiple times, so you need to control that.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">RUNNING</span><span class="p">.</span><span class="na">compareAndSet</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="kc">true</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// 第一次初始化会调用一次，去获取所有的数据，初始化缓存</span>
            <span class="c1">// fetch all group configs.</span>
            <span class="k">this</span><span class="p">.</span><span class="na">fetchGroupConfig</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">values</span><span class="p">());</span>
            <span class="kt">int</span> <span class="n">threadSize</span> <span class="o">=</span> <span class="n">serverList</span><span class="p">.</span><span class="na">size</span><span class="p">();</span>
            <span class="k">this</span><span class="p">.</span><span class="na">executor</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">threadSize</span><span class="p">,</span> <span class="n">threadSize</span><span class="p">,</span> <span class="mi">60L</span><span class="p">,</span> <span class="n">TimeUnit</span><span class="p">.</span><span class="na">SECONDS</span><span class="p">,</span>
                    <span class="k">new</span> <span class="n">LinkedBlockingQueue</span><span class="o">&lt;&gt;</span><span class="p">(),</span>
                    <span class="n">SoulThreadFactory</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="s">"http-long-polling"</span><span class="p">,</span> <span class="kc">true</span><span class="p">));</span>
            <span class="c1">// 我们看到这里用了线程池，其了一个任务，后面我们再跟进去看看</span>
            <span class="c1">// start long polling, each server creates a thread to listen for changes.</span>
            <span class="k">this</span><span class="p">.</span><span class="na">serverList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">server</span> <span class="o">-&gt;</span> <span class="k">this</span><span class="p">.</span><span class="na">executor</span><span class="p">.</span><span class="na">execute</span><span class="p">(</span><span class="k">new</span> <span class="n">HttpLongPollingTask</span><span class="p">(</span><span class="n">server</span><span class="p">)));</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"soul http long polling was started, executor=[{}]"</span><span class="p">,</span> <span class="n">executor</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// server list 感觉是多个soul-admin，应该是集群模式的高可用吧</span>
    <span class="c1">// server list 看着都要去请求一次，重复更新和操作是不是有点频繁，压力有些大？</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">fetchGroupConfig</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigGroupEnum</span><span class="p">...</span> <span class="n">groups</span><span class="p">)</span> <span class="kd">throws</span> <span class="n">SoulException</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">index</span> <span class="o">&lt;</span> <span class="k">this</span><span class="p">.</span><span class="na">serverList</span><span class="p">.</span><span class="na">size</span><span class="p">();</span> <span class="n">index</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">server</span> <span class="o">=</span> <span class="n">serverList</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">index</span><span class="p">);</span>
            <span class="k">try</span> <span class="p">{</span>
                <span class="k">this</span><span class="p">.</span><span class="na">doFetchGroupConfig</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">groups</span><span class="p">);</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">SoulException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// no available server, throw exception.</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="o">&gt;=</span> <span class="n">serverList</span><span class="p">.</span><span class="na">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">throw</span> <span class="n">e</span><span class="p">;</span>
                <span class="p">}</span>
                <span class="n">log</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="s">"fetch config fail, try another one: {}"</span><span class="p">,</span> <span class="n">serverList</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// 这个函数负责去获取请求，然后进行更新</span>
    <span class="c1">// 如果updated为false，会多睡上30秒，这个更新与否感觉挺重要的，但先放放，回头过来看</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">doFetchGroupConfig</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">server</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ConfigGroupEnum</span><span class="p">...</span> <span class="n">groups</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">StringBuilder</span> <span class="n">params</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">ConfigGroupEnum</span> <span class="n">groupKey</span> <span class="p">:</span> <span class="n">groups</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">params</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"groupKeys"</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="s">"="</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="n">groupKey</span><span class="p">.</span><span class="na">name</span><span class="p">()).</span><span class="na">append</span><span class="p">(</span><span class="s">"&amp;"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="c1">// 在这请求了所有的数据</span>
        <span class="n">String</span> <span class="n">url</span> <span class="o">=</span> <span class="n">server</span> <span class="o">+</span> <span class="s">"/configs/fetch?"</span> <span class="o">+</span> <span class="n">StringUtils</span><span class="p">.</span><span class="na">removeEnd</span><span class="p">(</span><span class="n">params</span><span class="p">.</span><span class="na">toString</span><span class="p">(),</span> <span class="s">"&amp;"</span><span class="p">);</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"request configs: [{}]"</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
        <span class="n">String</span> <span class="n">json</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="n">json</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="na">httpClient</span><span class="p">.</span><span class="na">getForObject</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">RestClientException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">message</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"fetch config fail from server[%s], %s"</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
            <span class="n">log</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="n">message</span><span class="p">);</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="n">SoulException</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="c1">// 这个判断有点意思，如果请求数据没有发生变化，那下一次请求将延长30秒</span>
        <span class="c1">// 这个updated如果判断得到的，我们一会回过头再去跟一跟</span>
        <span class="kt">boolean</span> <span class="n">updated</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="na">updateCacheWithJson</span><span class="p">(</span><span class="n">json</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">updated</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"get latest configs: [{}]"</span><span class="p">,</span> <span class="n">json</span><span class="p">);</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">// not updated. it is likely that the current config server has not been updated yet. wait a moment.</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"The config of the server[{}] has not been updated or is out of date. Wait for 30s to listen for changes again."</span><span class="p">,</span> <span class="n">server</span><span class="p">);</span>
        <span class="n">ThreadUtils</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">TimeUnit</span><span class="p">.</span><span class="na">SECONDS</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">updateCacheWithJson</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">json</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">JsonObject</span> <span class="n">jsonObject</span> <span class="o">=</span> <span class="n">GSON</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">json</span><span class="p">,</span> <span class="n">JsonObject</span><span class="p">.</span><span class="na">class</span><span class="p">);</span>
        <span class="n">JsonObject</span> <span class="n">data</span> <span class="o">=</span> <span class="n">jsonObject</span><span class="p">.</span><span class="na">getAsJsonObject</span><span class="p">(</span><span class="s">"data"</span><span class="p">);</span>
        <span class="c1">// if the config cache will be updated?</span>
        <span class="k">return</span> <span class="n">factory</span><span class="p">.</span><span class="na">executor</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      通过上面的代码，我们了解了开始启动的时候，会请求一次全量的数据进行本地缓存的更新
     </p>
     <p>
      下面我们来看看那个线程池相关的任务代码：
     </p>
     <p>
      从下面的代码大致可以看出是一个轮询的代码，其中出错就会在多睡一会
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>
    <span class="kd">class</span> <span class="nc">HttpLongPollingTask</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="p">{</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// 从代码可以大致的看出是一个轮询的函数，出错了就会多睡一会</span>
            <span class="k">while</span> <span class="p">(</span><span class="n">RUNNING</span><span class="p">.</span><span class="na">get</span><span class="p">())</span> <span class="p">{</span>
                <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">time</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">time</span> <span class="o">&lt;=</span> <span class="n">retryTimes</span><span class="p">;</span> <span class="n">time</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">try</span> <span class="p">{</span>
                        <span class="c1">// 我们跟一根这个函数</span>
                        <span class="n">doLongPolling</span><span class="p">(</span><span class="n">server</span><span class="p">);</span>
                    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
                        <span class="c1">// print warnning log.</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">time</span> <span class="o">&lt;</span> <span class="n">retryTimes</span><span class="p">)</span> <span class="p">{</span>
                            <span class="n">log</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="s">"Long polling failed, tried {} times, {} times left, will be suspended for a while! {}"</span><span class="p">,</span>
                                    <span class="n">time</span><span class="p">,</span> <span class="n">retryTimes</span> <span class="o">-</span> <span class="n">time</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
                            <span class="n">ThreadUtils</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">TimeUnit</span><span class="p">.</span><span class="na">SECONDS</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
                            <span class="k">continue</span><span class="p">;</span>
                        <span class="p">}</span>
                        <span class="c1">// print error, then suspended for a while.</span>
                        <span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"Long polling failed, try again after 5 minutes!"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
                        <span class="n">ThreadUtils</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">TimeUnit</span><span class="p">.</span><span class="na">MINUTES</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="n">log</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="s">"Stop http long polling."</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们接着看看 doLongPolling 的细节：
     </p>
     <p>
      从下面的代码中看到，这里请求了一个新的接口：listener，而且后面有判空操作后执行上面我们分析到请求全量数据的函数
     </p>
     <p>
      所以我们猜测这个HTTP轮询并不是每次都是拿全量的数据，而是有一个接口会返回数据是否更新的结果，如果发现数据有更新了，那就调用数据更新接口
     </p>
     <p>
      而且看代码它没有睡的操作，是实时轮询的
     </p>
     <p>
      睡的操作只有在 doFetchGroupConfig 中，当update为false的是才睡一个30秒，所以这个update的细节还是挺重要的，后面得跟一跟
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">doLongPolling</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">server</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">MultiValueMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">params</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LinkedMultiValueMap</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">ConfigGroupEnum</span> <span class="n">group</span> <span class="p">:</span> <span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">values</span><span class="p">())</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;?&gt;</span> <span class="n">cacheConfig</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="na">cacheConfigData</span><span class="p">(</span><span class="n">group</span><span class="p">);</span>
            <span class="n">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">join</span><span class="p">(</span><span class="s">","</span><span class="p">,</span> <span class="n">cacheConfig</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">cacheConfig</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">()));</span>
            <span class="n">params</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">(),</span> <span class="n">Lists</span><span class="p">.</span><span class="na">newArrayList</span><span class="p">(</span><span class="n">value</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="n">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpHeaders</span><span class="p">();</span>
        <span class="n">headers</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="n">MediaType</span><span class="p">.</span><span class="na">APPLICATION_FORM_URLENCODED</span><span class="p">);</span>
        <span class="n">HttpEntity</span> <span class="n">httpEntity</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpEntity</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">headers</span><span class="p">);</span>
        <span class="c1">// 它又请求了另外一个接口</span>
        <span class="n">String</span> <span class="n">listenerUrl</span> <span class="o">=</span> <span class="n">server</span> <span class="o">+</span> <span class="s">"/configs/listener"</span><span class="p">;</span>
        <span class="n">log</span><span class="p">.</span><span class="na">debug</span><span class="p">(</span><span class="s">"request listener configs: [{}]"</span><span class="p">,</span> <span class="n">listenerUrl</span><span class="p">);</span>
        <span class="n">JsonArray</span> <span class="n">groupJson</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">json</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="na">httpClient</span><span class="p">.</span><span class="na">postForEntity</span><span class="p">(</span><span class="n">listenerUrl</span><span class="p">,</span> <span class="n">httpEntity</span><span class="p">,</span> <span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">getBody</span><span class="p">();</span>
            <span class="n">log</span><span class="p">.</span><span class="na">debug</span><span class="p">(</span><span class="s">"listener result: [{}]"</span><span class="p">,</span> <span class="n">json</span><span class="p">);</span>
            <span class="n">groupJson</span> <span class="o">=</span> <span class="n">GSON</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">json</span><span class="p">,</span> <span class="n">JsonObject</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">getAsJsonArray</span><span class="p">(</span><span class="s">"data"</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">RestClientException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">message</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"listener configs fail, server:[%s], %s"</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="n">SoulException</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">groupJson</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// fetch group configuration async.</span>
            <span class="n">ConfigGroupEnum</span><span class="o">[]</span> <span class="n">changedGroups</span> <span class="o">=</span> <span class="n">GSON</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">groupJson</span><span class="p">,</span> <span class="n">ConfigGroupEnum</span><span class="o">[]</span><span class="p">.</span><span class="na">class</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">ArrayUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"Group config changed: {}"</span><span class="p">,</span> <span class="n">Arrays</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">));</span>
                <span class="c1">// 这里又调用强求全部数据的函数，猜测前面的那个listener的接口大致使用判断是否有数据更新</span>
                <span class="c1">// 有数据更新了，那就请求全量的数据进行更新</span>
                <span class="k">this</span><span class="p">.</span><span class="na">doFetchGroupConfig</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">changedGroups</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      跟一下start函数的调用栈，发现来到下面两个代码。可以看到是非常熟悉的Spring配置相关的了，那HTTP基本上是通过配置启动的了
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="nf">HttpSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpConfig</span> <span class="n">httpConfig</span><span class="p">,</span> <span class="kd">final</span> <span class="n">PluginDataSubscriber</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span>
                               <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;</span> <span class="n">authDataSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DataRefreshFactory</span><span class="p">(</span><span class="n">pluginDataSubscriber</span><span class="p">,</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="n">authDataSubscribers</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="na">httpConfig</span> <span class="o">=</span> <span class="n">httpConfig</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">serverList</span> <span class="o">=</span> <span class="n">Lists</span><span class="p">.</span><span class="na">newArrayList</span><span class="p">(</span><span class="n">Splitter</span><span class="p">.</span><span class="na">on</span><span class="p">(</span><span class="s">","</span><span class="p">).</span><span class="na">split</span><span class="p">(</span><span class="n">httpConfig</span><span class="p">.</span><span class="na">getUrl</span><span class="p">()));</span>
        <span class="k">this</span><span class="p">.</span><span class="na">httpClient</span> <span class="o">=</span> <span class="n">createRestTemplate</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">start</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataConfiguration</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">SyncDataService</span> <span class="nf">httpSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">HttpConfig</span><span class="o">&gt;</span> <span class="n">httpConfig</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">PluginDataSubscriber</span><span class="o">&gt;</span> <span class="n">pluginSubscriber</span><span class="p">,</span>
                                           <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">metaSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">authSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"you use http long pull sync soul data"</span><span class="p">);</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">HttpSyncDataService</span><span class="p">(</span><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">httpConfig</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">()),</span> <span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">pluginSubscriber</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">()),</span>
                <span class="n">metaSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">),</span> <span class="n">authSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      通过上面的分析，大致了解了HTTP同步的启动了，还有请求相关的代码，下面我们来分析一波非常重要的update相关的操作
     </p>
     <p>
      从上面代码跟踪下来，update的值是从这里来的，是HTTP同步的一个类，前面也遇到过，我们跟踪一下 dataRefresh.refresh 的操作
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">DataRefreshFactory</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">executor</span><span class="p">(</span><span class="kd">final</span> <span class="n">JsonObject</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="kt">boolean</span><span class="o">[]</span> <span class="n">success</span> <span class="o">=</span> <span class="p">{</span><span class="kc">false</span><span class="p">};</span>
        <span class="c1">// 代码的调用明显和websocket不一样，看着代码大意是五个类型都有进行更新，结合HTTP请求是全量数据，感觉确实需要五个都进行缓存更新</span>
        <span class="n">ENUM_MAP</span><span class="p">.</span><span class="na">values</span><span class="p">().</span><span class="na">parallelStream</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">dataRefresh</span> <span class="o">-&gt;</span> <span class="n">success</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">dataRefresh</span><span class="p">.</span><span class="na">refresh</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
        <span class="k">return</span> <span class="n">success</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      继续跟踪，来到下面的代码，在下面的代码中看到了关键的一步：updateCacheIfNeed
     </p>
     <p>
      在前面的代码中我们看到更新其实是全部更新的，五类型都更新的，这里就说明了，如果其中一个类型发送了更新，那update就为true
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractDataRefresh</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="n">DataRefresh</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="n">Boolean</span> <span class="nf">refresh</span><span class="p">(</span><span class="kd">final</span> <span class="n">JsonObject</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">boolean</span> <span class="n">updated</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
        <span class="n">JsonObject</span> <span class="n">jsonObject</span> <span class="o">=</span> <span class="n">convert</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">!=</span> <span class="n">jsonObject</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">fromJson</span><span class="p">(</span><span class="n">jsonObject</span><span class="p">);</span>
            <span class="c1">// 这段很关键，如果updateCacheIfNeed为true，那update就为true</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">updateCacheIfNeed</span><span class="p">(</span><span class="n">result</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">updated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
                <span class="c1">// 如果需要更新，进行更新操作</span>
                <span class="n">refresh</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="na">getData</span><span class="p">());</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">updated</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      继续跟下去，看到了非常关键的一些代码，可以大致看出是使用MD5值来判断是否重复的，感觉又学到了一手！
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PluginDataRefresh</span> <span class="kd">extends</span> <span class="n">AbstractDataRefresh</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">updateCacheIfNeed</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigData</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">updateCacheIfNeed</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">PLUGIN</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">updateCacheIfNeed</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigData</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">newVal</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ConfigGroupEnum</span> <span class="n">groupEnum</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// first init cache</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">GROUP_CACHE</span><span class="p">.</span><span class="na">putIfAbsent</span><span class="p">(</span><span class="n">groupEnum</span><span class="p">,</span> <span class="n">newVal</span><span class="p">)</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">ResultHolder</span> <span class="n">holder</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ResultHolder</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
        <span class="c1">// 这段是判断的，看到是判断的Md5的值，来看是否一样，感觉Soul-admin端的HTTP同步也做了很多的工作</span>
        <span class="n">GROUP_CACHE</span><span class="p">.</span><span class="na">merge</span><span class="p">(</span><span class="n">groupEnum</span><span class="p">,</span> <span class="n">newVal</span><span class="p">,</span> <span class="p">(</span><span class="n">oldVal</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
            <span class="c1">// must compare the last update time</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">StringUtils</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">oldVal</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">newVal</span><span class="p">.</span><span class="na">getMd5</span><span class="p">())</span> <span class="o">&amp;&amp;</span> <span class="n">oldVal</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">newVal</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">())</span> <span class="p">{</span>
                <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"update {} config: {}"</span><span class="p">,</span> <span class="n">groupEnum</span><span class="p">,</span> <span class="n">newVal</span><span class="p">);</span>
                <span class="n">holder</span><span class="p">.</span><span class="na">result</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
                <span class="k">return</span> <span class="n">newVal</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"Get the same config, the [{}] config cache will not be updated, md5:{}"</span><span class="p">,</span> <span class="n">groupEnum</span><span class="p">,</span> <span class="n">oldVal</span><span class="p">.</span><span class="na">getMd5</span><span class="p">());</span>
            <span class="k">return</span> <span class="n">oldVal</span><span class="p">;</span>
        <span class="p">});</span>
        <span class="k">return</span> <span class="n">holder</span><span class="p">.</span><span class="na">result</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      为了测试我们猜想的轮询是否正常，我们在HttpSyncDataService 的 doLongPolling ，等一段时间后，它自己就进入了断点，验证了我们的猜想基本是对的
     </p>
     <p>
      等待时间可能有点长，因为update为false时，睡的时间长一点
     </p>
     <p>
      我们又测试了在后台改变插件状态下的请求，发现能准确的探测到改变的是那一部分，感觉Soul-Admin那边也是做了很多的工作的
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">doLongPolling</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">server</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">MultiValueMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">params</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LinkedMultiValueMap</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>
        <span class="c1">// 这里把PLUGIN/RULE/SELECTOR/META_DATA/APP_AUTH都放进去了</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">ConfigGroupEnum</span> <span class="n">group</span> <span class="p">:</span> <span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">values</span><span class="p">())</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;?&gt;</span> <span class="n">cacheConfig</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="na">cacheConfigData</span><span class="p">(</span><span class="n">group</span><span class="p">);</span>
            <span class="n">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">join</span><span class="p">(</span><span class="s">","</span><span class="p">,</span> <span class="n">cacheConfig</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">cacheConfig</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">()));</span>
            <span class="n">params</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">(),</span> <span class="n">Lists</span><span class="p">.</span><span class="na">newArrayList</span><span class="p">(</span><span class="n">value</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="n">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpHeaders</span><span class="p">();</span>
        <span class="n">headers</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="n">MediaType</span><span class="p">.</span><span class="na">APPLICATION_FORM_URLENCODED</span><span class="p">);</span>
        <span class="n">HttpEntity</span> <span class="n">httpEntity</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpEntity</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">headers</span><span class="p">);</span>
        <span class="n">String</span> <span class="n">listenerUrl</span> <span class="o">=</span> <span class="n">server</span> <span class="o">+</span> <span class="s">"/configs/listener"</span><span class="p">;</span>
        <span class="n">log</span><span class="p">.</span><span class="na">debug</span><span class="p">(</span><span class="s">"request listener configs: [{}]"</span><span class="p">,</span> <span class="n">listenerUrl</span><span class="p">);</span>
        <span class="n">JsonArray</span> <span class="n">groupJson</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">json</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="na">httpClient</span><span class="p">.</span><span class="na">postForEntity</span><span class="p">(</span><span class="n">listenerUrl</span><span class="p">,</span> <span class="n">httpEntity</span><span class="p">,</span> <span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">getBody</span><span class="p">();</span>
            <span class="n">log</span><span class="p">.</span><span class="na">debug</span><span class="p">(</span><span class="s">"listener result: [{}]"</span><span class="p">,</span> <span class="n">json</span><span class="p">);</span>
            <span class="c1">// 没有更新时：{"code":200,"message":"success","data":[]} groupJson == []</span>
            <span class="c1">// 当我们admin后台改变限流插件的状态是，groupJson = ["PLUGIN"]</span>
            <span class="n">groupJson</span> <span class="o">=</span> <span class="n">GSON</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">json</span><span class="p">,</span> <span class="n">JsonObject</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">getAsJsonArray</span><span class="p">(</span><span class="s">"data"</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">RestClientException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">String</span> <span class="n">message</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"listener configs fail, server:[%s], %s"</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="n">SoulException</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">groupJson</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// fetch group configuration async.</span>
            <span class="n">ConfigGroupEnum</span><span class="o">[]</span> <span class="n">changedGroups</span> <span class="o">=</span> <span class="n">GSON</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">groupJson</span><span class="p">,</span> <span class="n">ConfigGroupEnum</span><span class="o">[]</span><span class="p">.</span><span class="na">class</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">ArrayUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"Group config changed: {}"</span><span class="p">,</span> <span class="n">Arrays</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">));</span>
                <span class="k">this</span><span class="p">.</span><span class="na">doFetchGroupConfig</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">changedGroups</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      到这里分析基本结束，进行一波总结
     </p>
     <h2 id="_3">
      总结
     </h2>
     <p>
      HTTP的数据同步流程大致如下：
     </p>
     <p>
      <img alt="" src="./picture/HTTP-bootstrap.png"/>
     </p>
     <p>
      从上图可得
     </p>
     <ul>
      <li>
       HttpSyncDataConfiguration : 配置启动
      </li>
      <li>
       HttpSyncDataService
       <ul>
        <li>
         进入时先请求一次数据更新本地缓存
        </li>
        <li>
         根据Soul-admin数量，启动线程池定时轮询
        </li>
       </ul>
      </li>
      <li>
       DataRefreshFactory ： 同时更新所有数据；使用MD5进行判重，数据没有发送改变则开始睡觉
      </li>
      <li>
       PluginDataRefresh ： 具体subscribe调用相应的类型的DataCache进行数据更新和删除，和websocket基本一致
      </li>
     </ul>
     <p>
      开完HTTP还是学到了很多东西的，数据变更接口和全量数据接口分离；数据没有更新时，多睡一会之类的
     </p>
     <p>
      还有些疑问：如果是多个Soul-admin，那多个Soul-admin之间的数据是如何同步的呢？这个留待后面研究
     </p>
     <h2 id="soul">
      Soul网关源码分析文章列表
     </h2>
     <h3 id="github">
      Github
     </h3>
     <ul>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB1-%E6%A6%82%E8%A7%88.md">
        Soul源码阅读（一） 概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB2-%E5%88%9D%E6%AD%A5%E8%BF%90%E8%A1%8C.md">
        Soul源码阅读（二）代码初步运行
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB3-%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%A6%82%E8%A7%88.md">
        Soul源码阅读（三）HTTP请求处理概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB4-dubbo%E8%AF%B7%E6%B1%82%E6%A6%82%E8%A7%88.md">
        Soul网关源码阅读（四）Dubbo请求概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB5-%E8%AF%B7%E6%B1%82%E7%B1%BB%E5%9E%8B%E6%8E%A2%E7%B4%A2.md">
        Soul网关源码阅读（五）请求类型探索
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB6-sofa%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%A6%82%E8%A7%88.md">
        Soul网关源码阅读（六）Sofa请求处理概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB7-%E9%99%90%E6%B5%81%E6%8F%92%E4%BB%B6%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（七）限流插件初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/0e6931519a84d5c603504b2c6a633698ac793b70/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB8-%E8%B7%AF%E7%94%B1%E5%8C%B9%E9%85%8D%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（八）路由匹配初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB9-%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（九）插件配置加载初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB10-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%AE%80%E5%8D%95%E6%8F%92%E4%BB%B6%E7%BC%96%E5%86%99.md">
        Soul网关源码阅读（十）自定义简单插件编写
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB11-%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E5%B0%8F%E7%BB%93.md">
        Soul网关源码阅读（十一）请求处理小结
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB12-%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（十二）数据同步初探-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB13-websocket%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE-Bootstrap%E7%AB%AF.md">
        Soul网关源码阅读（十三）Websocket同步数据-Bootstrap端
       </a>
      </li>
      <li>
       <p>
        <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB14-HTTP%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5-Bootstrap%E7%AB%AF.md">
         Soul网关源码阅读（十四）HTTP数据同步-Bootstrap端
        </a>
       </p>
      </li>
      <li>
       <p>
        <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%95%AA%E5%A4%96%E7%AF%871-HTTP%E7%A4%BA%E4%BE%8B%E5%8F%82%E6%95%B0%E8%AF%B7%E6%B1%82%E9%94%99%E8%AF%AF.md">
         Soul网关源码阅读番外篇（一） HTTP参数请求错误
        </a>
       </p>
      </li>
     </ul>
     <h3 id="_4">
      掘金
     </h3>
     <ul>
      <li>
       <a href="https://juejin.cn/post/6917864624423436296">
        Soul网关源码阅读（一） 概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917865804121767944">
        Soul网关源码阅读（二）代码初步运行
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917866538712334343">
        Soul网关源码阅读（三）请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917867369909977102">
        Soul网关源码阅读（四）Dubbo请求概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918575905962983438">
        Soul网关源码阅读（五）请求类型探索
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918736260467015693">
        Soul网关源码阅读（六）Sofa请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919348164944232455/">
        Soul网关源码阅读（七）限流插件初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919774553241550855/">
        Soul网关源码阅读（八）路由匹配初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920074307590684685/">
        Soul网关源码阅读（九）插件配置加载初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920142348617777166">
        Soul网关源码阅读（十）自定义简单插件编写
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596034171174925">
        Soul网关源码阅读（十一）请求处理小结
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596173925384206">
        Soul网关源码阅读（十二）数据同步初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596028505178125">
        Soul网关源码阅读（十三）Websocket同步数据-Bootstrap端
       </a>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6920597298674302983">
         Soul网关源码阅读（十四）HTTP数据同步-Bootstrap端
        </a>
        )
       </p>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6918947689564471309">
         Soul网关源码阅读番外篇（一） HTTP参数请求错误
        </a>
       </p>
      </li>
     </ul>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
